<?php
/**
* Copyright 2009 Waag Society <http://www.waag.org>
* This file is part of Annotrax.
*
* Annotrax is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License,
* or (at your option) any later version.
*
* Annotrax is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
* See the GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Annotrax.
* If not, see http://www.gnu.org/licenses/gpl.html
*/

?>
<?php
/**
 * Model for annotation
 *
 * @author Matthijs van Henten  <Matthijs@waag.org>
 * @license GNU GPL 2.0
 * @verson 0.1
 * @copyright Copyright (c) 2009, Waag Society
 *
 *
 */
class Default_Model_Annotation extends At_Model{
    const FETCH_LIMIT = 15;

	// class values
    protected $_id;
    protected $_duration;
    protected $_time;
    protected $_movieId;
    protected $_comment;
    protected $_channelId;
    protected $_userId;

    // properties
	private $start;
    private $mapper;
    private $keywords;
    private $score;
    private $movie;
    private $limit;
    private $offset;
	private $count;

	/**
	 * returns all class values as an array
	 * @return array $values
	 */
	public function toArray(){
        $collect = parent::toArray();
        $collect['time'] = (int) $collect['time'];
		$collect['duration'] = $this->getDuration(false);
		$collect['keywords'] = array();

		if( ( $keywords = $this->getKeywords() ) && null !== $keywords ){
			foreach( $keywords as $kw ){
				$collect['keywords'][] = $kw->toArray();
			}
		}


		return $collect;
	}

    /**
	 * Save ( insert or update ) this annotation
	 *
     * @return Default_Model_Annotation $this
     */
    public function save(){
        $this->getMapper()->save( $this );
        return $this;
    }

    /**
	 * Delete this annotation
     * @return void
     */
    public function delete(){
        if( null !== $this->_id ){
            $this->getMapper()->delete( $this );
        }
    }

    /**
     * Fetch the total amount of annotations that would be returned by
     * fetchAll without LIMIT/OFFSET. For use in pagers.
     *
     * @return int $count
     */
    public function getCount(){
		if( null === $this->count ){
			$this->count =  $this->getMapper()->fetchCount( $this );
		}
		return $this->count;
    }

    /**
	 * Retrieve all annotations. This function performs a paged-query controlled
	 * by setOffset and setLimit in this class. Select annotations per movie
	 * by setting movie_id, vice versa user_id and channel_id
	 *
     * @return array $array with Default_Model_Annotations
     */
    public function fetchAll(){
		$this->count = null;
        return $this->getMapper()->fetchAll( $this );
    }

    /**
     * Load all the values defined for this item, overriding current values.
     *
     * @return object $this;
     */
    protected function find(){
		if( null !== $this->_id ){
			$this->getMapper()->find( $this );
		}
		else if( null !== $this->_time
			&&   null !== $this->_movieId
			&&   null !== $this->_channelId
			&&	 null !== $this->_userId
		){
			$this->getMapper()->findByProperties( $this );
		}

		return $this;
    }

	/**
	 * Retrieve the comment part of this annotation.
	 * Triggers a DB search if not set.
	 *
	 * @return string $comment Comment
	 */
    public function getComment(){
        if( null === $this->_comment ){
            $this->find();
        }
        return $this->_comment;
    }

	/**
	 * Setter for comment. performs stripslashes
	 *
	 * @return $this Fluent interface.
	 */
	public function setComment( $comment ){
		if( null !== $comment ){
			$this->_comment = stripslashes( $comment );
		}
		return $this;
	}

    /**
     * Fetch keywords for this annotation
     * @return array Array of Default_Model_Keyword objets
     */
    public function getKeywords(){
        if( null === $this->keywords && null !== $this->_id ){
            $keyword = new Default_Model_Keyword();
			$keyword->setAnnotation( $this );

			$this->keywords = $keyword->fetchAll();
        }

        return $this->keywords;
    }

	/**
	 * Set keywords for this annotation
	 *
	 * @param array Keywords. Array of Model_Keyword or plain text words.
	 * @return $this fluent interface.
	 */
    public function setKeyWords( array $keywords ){
		$this->keywords = array();

		foreach( $keywords as $keyword ){
			if( $keyword instanceof Default_Model_Keyword ){
				$this->keywords[] = $keyword;
			}

			// @todo verify this
			$this->keywords = array_unique( $this->keywords );
		}
        return $this;
    }

	/**
	 * Retrieve movie matching $_movieId
	 *
	 * @todo Shouldn't the movie-object be instantiated outside this class?
	 * @return Default_Model_Movie
	 */
    public function getMovie(){
        if( null === $this->_movieId ){
            $this->find();
        }

        if( null === $this->movie ){
            $this->movie = new Default_Model_Movie( $this->_movieId );
        }

        return $this->movie;
    }

    /**
     * Format time-uri as t=HH:MM:SS/HH:MM:SS
     *
     * @return string Time url
    */
    public function getTimeUri(){
        return $this->getStart() . '/' . $this->getEnd();
    }

	/**
	 * Format a URL to the movie. This function exists solely as
	 * convenience within temp.
	 *
	 * @return string $uri
	 */
	public function getMovieUri(){
		$uri = array( AT_MOVIE_PATH, $this->getMovie()->filename. '?t=' . $this->getTimeUri() );
		return join( '/', $uri );
	}

	/**
	 * Return an url to this annotation
	 *
	 * @return string $url ( including movie file etc. )
	 */
	public function getUri(){
        $uri = array('/movie', 'play', $this->_movieId, '?a=' . $this->_id . '&t=' . $this->getTimeUri() );
        return join( '/', $uri );
    }

    /**
     * Return start either formatted or as integer
     *
     * @param bool $format Format the time as a time code
     * @return string Start position in 00:00:00 or integer
     */
    public function getStart( $format = true ){
        if( null === $this->_time ){
			$this->_time = 0;
            $this->find();
        }

        if( $format ){
            return $this->makeTimeCode( $this->_time );
        }

        return $this->_time;
    }

	/**
	 * Set duration.
	 *
	 * @param mixed $duration Duration in timecode format or integer
	 * @return $this fluent interface
	 */
    public function setDuration( $duration ){
        if( $this->isTimeCode( $duration ) ){
            $duration = $this->parseTimeCode( $duration );
        }

		if( is_numeric( $duration ) ){
			$config = $this->getMovie()->getInfo();

			if( $this->getStart(false) + $duration  <= $config->duration ){
				$this->_duration = $duration;
			}
			else{
				$this->_duration = $config->duration - $this->getStart(false);
			}
        }
        return $this;
    }

	/**
	 * Set time. This funciton tries to deterimine wether you are feeding a
	 * timeCode or an integer.
	 *
	 * @param mixed $time Timecode or int
	 * @return $this fluent interface
	 */
    public function setTime( $time ){
		if( $this->isTimeCode( $time ) ){
            $time = $this->parseTimeCode( $time );
        }
		if( is_numeric( $time ) ){
			$config = $this->getMovie()->getInfo();
			if( $time > 0 && $time < $config->duration ){
				$this->_time = $time;
			}
			else{
				$this->_time = 0;
			}
		}
        return $this;
    }

	/**
	 * Calculate end based on start and duration.
	 * @return string timecode
	 */
    public function getEnd(){
        return $this->makeTimeCode( $this->getStart(false) + $this->getDuration(false) );
    }

    /**
     * @param bool $format Format the time as a time code
     * @return mixed Duration as timecode or integer
     */
    public function getDuration( $format = true ){
        if( null === $this->_duration ){
			$this->_duration = 10;
            $this->find();
        }

        if( $format ){
            return $this->makeTimeCode( $this->_duration );
        }
        return $this->_duration;
    }

	/**
	 * Check if $code is a valid timecode.
	 *
	 * @param mixed $code Timecode
	 * @return bool true or false
	 */
    private function isTimeCode( $code ){
        // disabled string-length cehcking.
        // only check if string contains at least numeric values.
        $pieces = explode(':', $code );
        if( count( $pieces ) === 3 ){
            foreach( $pieces as $n ){
                if( ! is_numeric( $n ) ){
                    return false;
                }
            }
            // I believe you.
            return true;
        }
        return false;
    }

    private function parseTimeCode( $code ){
        list( $hour, $min, $sec ) = explode( ':', $code );
        $time = $sec + ( 60 * $min) + ( 3600 * $hour);
        return $time;
    }

    private function makeTimeCode( $time ){
		$timeCode = sprintf('%02d:%02d:%02d', floor($time/3600), floor($time/60), $time%60 );
		return $timeCode;
    }

    public function setOffset( $offset ){
		if( null !== $offset && is_numeric( $offset ) ){
	        $this->offset = (int) $offset;
		}
        return $this;
    }

    public function setLimit( $limit ){
		if( null !== $limit && is_numeric( $limit ) ){
	        $this->limit = (int) $limit;
		}
        return $this;
    }

    public function getOffset(){
        if( null === $this->offset ){
            $this->setOffset( 0 );
        }
        return $this->offset;
    }

    public function getLimit(){
        if( null === $this->limit ){
            $this->setLimit( self::FETCH_LIMIT );
        }

        return $this->limit;
    }
}
